package org.chene.elastic.util;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import org.chene.elastic.annotation.EsField;
import org.chene.elastic.bean.EsFieldInfo;
import org.chene.elastic.bean.EsTypeInfo;
import org.chene.elastic.enumeration.EsAnalyzer;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingResponse;
import org.elasticsearch.client.transport.TransportClient;
import org.elasticsearch.common.lang3.StringUtils;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.springframework.stereotype.Service;

import com.esotericsoftware.reflectasm.MethodAccess;

@Service
public class MappingUtil {

	//private static final Logger LOGGER = LoggerFactory.getLogger(MappingUtil.class);

	private static final ConcurrentMap<Class<?>, EsTypeInfo> ES_TYPE_CACHE = new ConcurrentHashMap<Class<?>, EsTypeInfo>();

	public static EsTypeInfo analyze(String index, TransportClient transportClient, Object document) throws IOException {
		if (null == document) {
			return null;
		}

		EsTypeInfo esType = new EsTypeInfo();
		Class<?> root = document.getClass();

		if (ES_TYPE_CACHE.containsKey(root)) {
			return ES_TYPE_CACHE.get(root);
		}

		analyzeBean(index, transportClient, esType, root);

		ES_TYPE_CACHE.put(root, esType);
		return esType;
	}

	private static void analyzeBean(String index, TransportClient transportClient, EsTypeInfo esType, Class<?> root)
			throws IOException {
		MethodAccess methodAccess = MethodAccess.get(root);
		Field[] fileds = root.getDeclaredFields();
		EsFieldInfo esFieldInfo;

		esType.setTarget(root);
		esType.setMethodAccess(methodAccess);

		for (Field field : fileds) {
			if (!Modifier.isStatic(field.getModifiers())) {
				esFieldInfo = new EsFieldInfo();
				esFieldInfo.setFieldName(field.getName());
				esFieldInfo.setTarget(field);
				esFieldInfo.setTargetClazz(root);
				esFieldInfo.setTargetMethodAccess(methodAccess);
				esFieldInfo.setIndex(methodAccess.getIndex("get" + firstCharacterUppercase(field.getName())));
				esFieldInfo.setEsField(field.getAnnotation(EsField.class));
				if (isBasicTypeField(field)) {
					esType.add(esFieldInfo);
				} else {
					EsTypeInfo beanFieldEsTypeInfo = new EsTypeInfo();
					analyzeBean(index, transportClient, beanFieldEsTypeInfo, getFieldDeclaredType(field));
					esType.add(esFieldInfo, beanFieldEsTypeInfo);
				}
			}
		}

		// 构建索引
		buildTypeIndex(transportClient, index, esType);
	}

	private static String firstCharacterUppercase(String name) {
		if (StringUtils.isBlank(name)) {
			return name;
		}
		if (name.length() == 1) {
			return String.valueOf(name.charAt(0)).toUpperCase();
		}

		return String.valueOf(name.charAt(0)).toUpperCase() + name.substring(1);
	}

	private static boolean isBasicTypeField(Field field) {
		return isBasicType(getFieldDeclaredType(field));
	}

	private static Class<?> getFieldDeclaredType(Field field) {
		if (List.class.isAssignableFrom(field.getType())) {
			Type ft = field.getGenericType();
			if (ft instanceof ParameterizedType) {
				ParameterizedType pt = (ParameterizedType) ft;
				return (Class<?>) pt.getActualTypeArguments()[0];
			}
		}

		if (field.getType().isArray()) {
			return (Class<?>) field.getType().getComponentType();
		}

		return field.getType();
	}

	/**
	 * 判断是否是简单的对象.
	 *
	 * @param cls
	 * @return
	 */
	private static boolean isBasicType(Class<?> cls) {
		if (cls.isPrimitive() || cls == String.class || cls == Integer.class || cls == BigDecimal.class
				|| cls == Date.class || cls == Long.class) {
			return true;
		} else {
			return false;
		}
	}

	public static boolean buildTypeIndex(TransportClient transportClient, String index, EsTypeInfo esType)
			throws IOException {
		XContentBuilder builder = XContentFactory.jsonBuilder().startObject();
		// 子属性放到该索引下
		builder.startObject("properties");

		for (EsFieldInfo field : esType.getBasicFields()) {
			if (null == field.getEsField()) {
				continue;
			}
			builder.startObject(field.getFieldName()).field("store", field.getEsField().isStore())
					.field("type", field.getEsField().fieldType().getTypeValue());
			EsAnalyzer esAnalyzer = field.getEsField().analyzerType();
			if (esAnalyzer == EsAnalyzer.ansj_auto) {
				builder.field("indexAnalyzer", EsAnalyzer.ansj_index.name()).field("searchAnalyzer",
						EsAnalyzer.ansj_query.name());
			} else if (esAnalyzer != EsAnalyzer.not_analyzed) {
				builder.field("indexAnalyzer", esAnalyzer.name()).field("searchAnalyzer", esAnalyzer.name());
			} else {
				builder.field("index", esAnalyzer.name());
			}
			builder.endObject();
		}

		builder.endObject();

		PutMappingResponse response = transportClient.admin().indices().preparePutMapping(index)
				.setType(esType.getTarget().getSimpleName()).setSource(builder).execute().actionGet();
		return response.isAcknowledged();
	}

	public static List<Object> listBean(EsTypeInfo esType, Object document) {
		List<Object> beans = new ArrayList<Object>();
		beans.add(document);

		for (Entry<EsFieldInfo, EsTypeInfo> childEntry : esType.getBeanFields().entrySet()) {
			Object beanFieldValue = childEntry.getKey().getTargetMethodAccess()
					.invoke(document, childEntry.getKey().getIndex());
			beans.add(beanFieldValue);

			listBean(childEntry.getValue(), beanFieldValue);
		}

		return beans;
	}
}
